/*
 * Copyright (c) 2011 Ondrej Lapacek
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you
 * may not use this file except in compliance with the License. You may
 * obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * permissions and limitations under the License.
 */

/**
 * @author      Ondrej Lapacek
 * @version     1.0, 09/12/11
 * @since       1.0
 */

package cz.muni.fi.android.assistivecamera.sensor;

import java.util.List;

import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import cz.muni.fi.android.assistivecamera.AssistiveCameraActivity;
 
public class OrientationManager {
	
    private static SensorManager sensorManager;
    private static OrientationListener listener;
 
    // Orientation is supported
    private static Boolean supported;
    // Orientation is running
    private static boolean running = false;
    
	static float[] aValues = new float[3];
	static float[] mValues = new float[3];

    private enum Side {
        horizontal,
        lost;
    }	
 
    // Returns true if the manager is listening to orientation changes
    public static boolean isListening() {
        return running;
    }
 
    // Unregisters listeners
    public static void stopListening() {
        running = false;
        try {
            if (sensorManager != null && sensorEventListener != null) {
                sensorManager.unregisterListener(sensorEventListener);
            }
        } catch (Exception e) {}
    }
 
    public static boolean isSupported() {
        if (supported == null) {
            if (AssistiveCameraActivity.getContext() != null) {
                sensorManager = (SensorManager) AssistiveCameraActivity.getContext().getSystemService(Context.SENSOR_SERVICE);
                List<Sensor> accSensors = sensorManager.getSensorList(Sensor.TYPE_ACCELEROMETER);
                List<Sensor> magSensors = sensorManager.getSensorList(Sensor.TYPE_MAGNETIC_FIELD);
                supported = new Boolean(accSensors.size() > 0 && magSensors.size() > 0);
            } else {
                supported = Boolean.FALSE;
            }
        }
        return supported;
    }
 
    // Registers a listeners and start listening
    public static void startListening(
            OrientationListener orientationListener) {
        sensorManager = (SensorManager) AssistiveCameraActivity.getContext().getSystemService(Context.SENSOR_SERVICE);
        List<Sensor> accSensors = sensorManager.getSensorList(Sensor.TYPE_ACCELEROMETER);
        List<Sensor> magSensors = sensorManager.getSensorList(Sensor.TYPE_MAGNETIC_FIELD);
        if (accSensors.size() > 0 && magSensors.size() > 0) {
            boolean runningAcc = sensorManager.registerListener(sensorEventListener, 
            		accSensors.get(0), 
                    SensorManager.SENSOR_DELAY_FASTEST);
            boolean runningMag = sensorManager.registerListener(sensorEventListener, 
            		magSensors.get(0),
                    SensorManager.SENSOR_DELAY_FASTEST);
            running = (runningAcc && runningMag);
            listener = orientationListener;
        }
    }
    
    private static float[] calculateOrientation() {
        float[] values = new float[3];
        float[] R = new float[9];
        float[] outR = new float[9];

        SensorManager.getRotationMatrix(R, null, aValues, mValues);
        SensorManager.remapCoordinateSystem(R, 
                                            SensorManager.AXIS_X, 
                                            SensorManager.AXIS_Z, 
                                            outR);

        SensorManager.getOrientation(outR, values);

        // Convert from Radians to Degrees.
        values[0] = (float) Math.toDegrees(values[0]);
        values[1] = (float) Math.toDegrees(values[1]);
        values[2] = (float) Math.toDegrees(values[2]);

        return values;
    }

    private static SensorEventListener sensorEventListener = 
        new SensorEventListener() {

        Side currentSide = null;
        Side oldSide = null;
        float azimuth;
        float pitch;
        float roll;
        float[] values;
 
        public void onAccuracyChanged(Sensor sensor, int accuracy) {}
 
        public void onSensorChanged(SensorEvent event) {
        	
        	synchronized (this) {
	        	if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER)
	                aValues = event.values.clone();
	            if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD)
	                mValues = event.values.clone();
        	}
        	
        	if (aValues != null && mValues != null) {
	            values = calculateOrientation();
	 
	        	azimuth = Math.round(values[0]);
	        	azimuth = (azimuth + 360)%360;
	            pitch = Math.round(values[1]);
	            roll = Math.round(-values[2]);
        	}
            
            if (pitch > -10 && pitch < 10 
            		&& roll > 95 && roll < 105) {
                // right side up
            	currentSide = Side.horizontal;
            } else if (!(pitch > -15 && pitch < 15 
            		&& roll > 90 && roll < 110)) {
            	currentSide = Side.lost;
            }
            
            if (currentSide != null && !currentSide.equals(oldSide)) {
                switch (currentSide) {
                    case horizontal : 
                    	listener.onPhotoOrientation(1);
                        break;
                    case lost : 
                    	listener.onPhotoOrientation(0);
                        break;
                }
                oldSide = currentSide;
            }
 
            // forwards orientation to the OrientationListener
            listener.onOrientationChanged(azimuth);
        }
 
    };
 
}
